/*
 * Copyright (c) [2005] [Jeffrey Moore]
 *
 * Redistributions in source code form must reproduce the above copyright and 
 * this condition.
 *
 * The contents of this file are subject to the Sun Project JXTA License 
 * Version 1.1 (the "License"); you may not use this file except in compliance 
 * with the License. A copy of the License is available at 
 * http://www.jxta.org/jxta_license.html.
 *
 */

/*
 * NetworkedDeviceMonitor.java
 *
 * Created on June 4, 2005, 6:37 AM
 */

package net.jxta.myjxta.plugins.vijxta;

import net.jxta.endpoint.ByteArrayMessageElement;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.logging.Logging;
import net.jxta.myjxta.dialog.DialogMessage;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.MemoryCacheImageInputStream;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.*;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Jeff Moore
 */
public final class NetworkedDeviceMonitorControl extends Thread implements Monitor, DeviceMonitorControl {

    static final Logger LOG = Logger.getLogger(NetworkedDeviceMonitorControl.class.getName());

    private static final String REFRESH_RATE_UNIT_LABEL = "seconds";

    private LinkedList outgoingBuffer = null;

    private ViJxtaDialog viJxtaDialog = null;

    private static final int SERVER_CHUNK_READ_SIZE = 1460;

    private int refreshRate = 1;

    private long imageCaptureDelay = 0;

    private static final int DEFAULT_IMAGES_PER_MESSAGE = 1;

    private int imagesPerMessage = DEFAULT_IMAGES_PER_MESSAGE;

    private MonitorComponent monitorComponent = null;

    private static final int MINIMUM_REFRESH_RATE = 15;

    private static final int MAXIMUM_REFRESH_RATE = 1;

    private static final int DEFAULT_REFRESH_RATE = MINIMUM_REFRESH_RATE;

    private int imageCompression = Monitor.DEFAULT_IMAGE_COMPRESSION;

    private MyLock captureLock = null;

    private MyLock monitorPausedLock = null;

    private MyLock transmitPausedLock = null;

    private String deviceError = null;

    private MyLock bufferStarvedLock = null;

    private long imageEncodeTime = 0;

    private long averageImageEncodeTime = 0;

    private DialogMessage templateMessage = null;

    private int bytesSent = 0;

    private int messagesSent = 0;

    private int monitorState = STOPPED;

    private int transmitState = STOPPED;

    private boolean transmit = false;

    private Hashtable refreshRateTable = null;

    private Hashtable refreshRateLabelTable = null;

    private Thread dispatchThread = null;

    private String urlString = null;

    private String userName = null;

    private String password = null;

    private boolean authenticated = false;

    private String mimeType = null;

    private String responseString = null;

    private Vector deviceErrorListeners = null;


    public NetworkedDeviceMonitorControl(ViJxtaCallControl callControl) {
        LOG.setLevel(Level.INFO);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("NetworkedDeviceMonitorControl Instantiated");
        }

        deviceErrorListeners = new Vector();

        refreshRateTable = new Hashtable();

        refreshRateTable.put(new Integer(1), new Long((long) this.SECOND * 1));
        refreshRateTable.put(new Integer(3), new Long((long) this.SECOND * 2));
        refreshRateTable.put(new Integer(5), new Long((long) this.SECOND * 5));
        refreshRateTable.put(new Integer(7), new Long((long) this.SECOND * 7));
        refreshRateTable.put(new Integer(10), new Long((long) this.SECOND * 10));
        refreshRateTable.put(new Integer(15), new Long((long) this.SECOND * 15));

        refreshRateLabelTable = new Hashtable();


        refreshRateLabelTable.put(new Integer(1), new JLabel("1"));
        refreshRateLabelTable.put(new Integer(3), new JLabel("3"));
        refreshRateLabelTable.put(new Integer(5), new JLabel("5"));
        refreshRateLabelTable.put(new Integer(7), new JLabel("7"));
        refreshRateLabelTable.put(new Integer(10), new JLabel("10"));
        refreshRateLabelTable.put(new Integer(15), new JLabel("15"));


        this.imageCaptureDelay = ((Long) refreshRateTable.get(new Integer(this.DEFAULT_FRAMES_PER_SECOND))).longValue();

        this.viJxtaDialog = (ViJxtaDialog) callControl.getDialog();

        outgoingBuffer = new LinkedList();

        captureLock = new MyLock();

        captureLock.setLocked(true);

        monitorPausedLock = new MyLock();

        transmitPausedLock = new MyLock();

        bufferStarvedLock = new MyLock();

        templateMessage = new DialogMessage(callControl.getDialog().getGroup()
                .getPeerGroup().getPeerName(), null, callControl.getDialog().getGroup()
                .getPeerGroup().getPeerGroupID().toString(), callControl.getDialog()
                .getGroup().getPeerGroup().getPeerGroupName());

        /** consumer thread... takes from the queue and sends message*/
        dispatchThread = new Thread() {

            public void run() {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("Transmit (Consumer) Thread: RUN");
                }
                while (true) {
                    if (getTransmitState() == Monitor.STOPPED) {
                        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                            LOG.info("run : Stopped");
                        }
                        // we should send any remaining data in buffer first
                        break;
                    }
                    if (getTransmitState() == Monitor.PAUSED) {
                        synchronized (transmitPausedLock) {
                            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                                LOG.info("run : tramsmit Paused");
                            }
                            try {
                                transmitPausedLock.wait();
                            }
                            catch (InterruptedException ix) {
                                ix.printStackTrace();
                            }
                        }
                    }
                    if (outgoingBuffer.size() >= getImagesPerMessage()) {
                        dispatchImage(getImagesPerMessage());
                    } else {
                        try {
                            synchronized (bufferStarvedLock) {
                                bufferStarvedLock.setLocked(true);
                                bufferStarvedLock.wait();
                            }
                        }
                        catch (InterruptedException ix) {
                            ix.printStackTrace();
                        }
                    }
                }
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("Transmit (Consumer) Thread: ENDED");
                }
            }
        };
    }


    /**
     * producer... runs every n ms (the refreshRate), sets the capture flag
     */
    public void run() {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("Image Capture (Producer) Thread RUN");
        }
        while (true) {

            /** module state mechanics*/
            if (getMonitorState() == this.PAUSED) {

                synchronized (monitorPausedLock) {
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("run : monitor Paused");
                    }
                    try {

                        monitorPausedLock.wait();

                    } catch (InterruptedException ix) {
                        ix.printStackTrace();
                    }
                }
            }

            if (getMonitorState() == this.STOPPED) {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("run : Stopped");
                }

                break;
            }

            captureImage();


            try {

                Thread.sleep(getImageCaptureDelay());

            } catch (InterruptedException ix) {
                ix.printStackTrace();
            }


        }

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("Image Capture (Producer) Thread Ended");
        }
    }

    protected void dispatchImage(int imagesInThisMessage) {

        if (outgoingBuffer.size() >= imagesInThisMessage) {

            byte[] imageBytes = null;

            DialogMessage msg = getNewTemplateMessage();

            StringMessageElement commandElement = new StringMessageElement(
                    ViJxtaCallControl.TAG_SESSION_COMMAND, ViJxtaCallControl.COMMAND_VIJXTA_DATA, null);

            msg.addMessageElement(ViJxtaCallControl.TAG_SESSION_COMMAND, commandElement);

            imageBytes = ((ByteArrayOutputStream) outgoingBuffer.getFirst()).toByteArray();

            outgoingBuffer.removeFirst();

            ByteArrayMessageElement imageElement = new ByteArrayMessageElement(
                    ViJxtaCallControl.TAG_IMAGE_DATA, null, imageBytes, 0, null);

            msg.addMessageElement(ViJxtaCallControl.TAG_IMAGE_DATA, imageElement);

            this.messagesSent = this.messagesSent + 1;

            this.bytesSent = this.bytesSent + imageBytes.length;

            this.viJxtaDialog.dispatch(msg);

        }
    }

    public Hashtable getRefreshRateLabelTable() {

        return this.refreshRateLabelTable;
    }

    public Dimension getFormatSize() {

        return this.getMonitorComponent().getPreferredSize();
    }


    public void resetMonitor() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("resetMonitor");
        }
        if (this.getTransmitState() == this.STOPPED) {

            this.stopMonitorCapture();

            this.stopMonitor();

            this.releaseHardware();
        }

    }

    public int getTransmitState() {

        return this.transmitState;
    }


    public String getRefreshRateUnitLabel() {

        return this.REFRESH_RATE_UNIT_LABEL;
    }

    public void setTransmitState(int transmitState) {

        this.transmitState = transmitState;
    }


    public void pauseMonitor() {

        this.setMonitorState(this.PAUSED);
    }

    public void resumeMonitor() {

        this.setMonitorState(this.STARTED);

        synchronized (monitorPausedLock) {

            monitorPausedLock.notifyAll();
        }
    }

    public void pauseTransmit() {

        this.setTransmitState(this.PAUSED);
    }

    public void resumeTransmit() {

        this.setTransmitState(this.STARTED);

        synchronized (transmitPausedLock) {

            transmitPausedLock.notifyAll();
        }
    }

    public long getImageCaptureDelay() {

        return this.imageCaptureDelay;
    }

    public void setRefreshRate(int refreshRate) {

        if (refreshRate >= this.MINIMUM_REFRESH_RATE &&
                refreshRate <= this.MAXIMUM_REFRESH_RATE) {

            this.refreshRate = refreshRate;

            this.imageCaptureDelay = ((Long) refreshRateTable.get(new Integer(refreshRate))).longValue();
        } else {

            this.refreshRate = this.DEFAULT_REFRESH_RATE;

            this.imageCaptureDelay = ((Long) refreshRateTable.get(new Integer(this.DEFAULT_REFRESH_RATE))).longValue();
        }

    }

    public int getRefreshRate() {

        return this.refreshRate;
    }

    public void setURL(String urlString) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("setUrl " + urlString);
        }

        this.urlString = urlString;
    }

    private URL getURL(String urlString) {
        URL url = null;

        if (urlString != null) {

            try {

                url = new URL(urlString);
            } catch (MalformedURLException mux) {
                mux.printStackTrace();
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("Bad URL!! Make sure URL starts wtih http://");
                }
                setDeviceError("Bad URL!! Make sure URL starts wtih http://");


                notifyDeviceErrorListeners();
                return null;
            }
        } else {
            setDeviceError("Bad URL!! Make sure URL starts wtih http://");

            notifyDeviceErrorListeners();
            return null;
        }


        return url;
    }


    public void setAuthenticated(boolean authenticated) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            //LOG.info ("authenticated "+authenticated);
        }
        this.authenticated = authenticated;
    }

    public boolean isAuthenticated() {

        return this.authenticated;
    }

    public void setUserName(String userName) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            //LOG.info ("userName "+userName);
        }

        this.userName = userName;
    }

    public void setPassword(String password) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            //LOG.info ("password " + password);
        }

        this.password = password;
    }

    public String getUserName() {

        return userName;
    }

    public String getPassword() {

        return password;
    }

    public String getURLString() {

        return urlString;
    }

    private String getCredentials() {

        String rtn = null;

        if (getUserName() != null && getPassword() != null) {

            rtn = userName + ":" + password;
        }

        return rtn;
    }

    private String getEncodedCredentials() {

        String encodedCredentials = null;

        String credentials = getCredentials();
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            //LOG.info ("username:password "+getUserName ()+":"+getPassword ());

            //LOG.info ("credentials "+credentials);
        }
        if (credentials != null) {

            encodedCredentials = Base64.encode(credentials);
        }

        return encodedCredentials;
    }


    public void addErrorListener(DeviceMonitorControl.DeviceErrorListener listener) {

        synchronized (deviceErrorListeners) {
            deviceErrorListeners.add(listener);
        }
    }

    private void notifyDeviceErrorListeners() {

        // this isn't thread safe
        Iterator itr = deviceErrorListeners.iterator();

        while (itr.hasNext()) {

            ((DeviceMonitorControl.DeviceErrorListener) itr.next()).
                    deviceErrorEncountered(getDeviceError());
        }
    }

    private String getDeviceError() {

        return this.deviceError;
    }

    private void setDeviceError(String deviceError) {

        this.deviceError = deviceError;
    }

    private HttpURLConnection getHttpConnection(URL url, boolean authenticate) {

        HttpURLConnection camServer = null;

        try {

            camServer = (HttpURLConnection) url.openConnection();

        } catch (IOException iox) {

            iox.printStackTrace();
            setDeviceError("CamServer I/O Error.");
            notifyDeviceErrorListeners();
            return null;

        } catch (NullPointerException npx) {
            npx.printStackTrace();
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("URL is NULL!");
            }

            setDeviceError("Bad URL");
            notifyDeviceErrorListeners();
            return null;

        } catch (ClassCastException ccx) {
            ccx.printStackTrace();
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("This is not a HttpURLConnection");
            }

            setDeviceError("Incorrect URL connection type. Expected URL connection.");
            notifyDeviceErrorListeners();
            return null;

        } catch (Exception x) {
            x.printStackTrace();

            setDeviceError("General CamServer Error.");
            notifyDeviceErrorListeners();
            return null;
        }

        if (camServer == null) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                //LOG.info ("authenticating");
            }

            setDeviceError("CamServer Error.");
            notifyDeviceErrorListeners();
            return null;
        }


        try {
            camServer.setAllowUserInteraction(false);
            camServer.setDoInput(true);
            camServer.setRequestMethod("GET");
        } catch (ProtocolException px) {
            px.printStackTrace();

            setDeviceError("CamServer protocol error: " + px.getMessage());
            notifyDeviceErrorListeners();
            return null;
        } catch (Exception x) {
            x.printStackTrace();

            setDeviceError("CamServer General Error: " + x.getMessage());
            notifyDeviceErrorListeners();
            return null;
        }

        camServer.setRequestProperty("Keep-Alive", "300");

        if (authenticate) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                // LOG.info ("authenticating");
            }
            camServer.setRequestProperty("Authorization", "Basic " + getEncodedCredentials());
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                //LOG.info ("NOT authenticating");
            }
        }

        return camServer;

    }

    private void setMimeType(String mimeType) {

        this.mimeType = mimeType;
    }

    public String getMimeType() {

        return this.mimeType;
    }

    public String getResponseString() {

        return this.responseString;
    }

    public void setResponseString(String responseString) {

        this.responseString = responseString;
    }

    private ByteArrayOutputStream requestURLImage() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            //LOG.info("requestURLImage");
        }

        ByteArrayOutputStream imageByteArray = new ByteArrayOutputStream();
        BufferedInputStream in = null;
        int contentLength = 0;

        HttpURLConnection camServer = getHttpConnection(getURL(getURLString()), isAuthenticated());

        try {
            camServer.connect();

        } catch (SocketTimeoutException stx) {
            stx.printStackTrace();
            setDeviceError("CamServer Timeout Error");
            notifyDeviceErrorListeners();
            return null;
        } catch (IOException iox) {
            iox.printStackTrace();
            setDeviceError("CamServer I/O Error.");
            notifyDeviceErrorListeners();
            return null;
        } catch (Exception x) {
            x.printStackTrace();
            setDeviceError("General CamServer Device Access Error.");
            notifyDeviceErrorListeners();
            return null;
        }

        //dont do anythign more and return null if content length is nada or 1
        try {
            in = new BufferedInputStream(camServer.getInputStream());
        } catch (IOException iox) {
            iox.printStackTrace();
            setDeviceError("CamServer I/O Error.");
            notifyDeviceErrorListeners();
            return null;
        }

        try {
            setResponseString(camServer.getResponseMessage());
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                //LOG.info ("response "+getResponseString ());
            }
            setMimeType(camServer.getHeaderField("Content-Type"));
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                //LOG.info("mimeType "+getMimeType());
                //LOG.info("content length "+camServer.getHeaderField ("Content-Length"));
            }
            String s = camServer.getHeaderField("Content-Length");
            if (s != null)
                contentLength = Integer.valueOf(s).intValue();
        } catch (IOException iox) {
            iox.printStackTrace();
            setDeviceError("CamServer I/O Error");
            notifyDeviceErrorListeners();
            return null;
        } catch (NumberFormatException nfx) {
            nfx.printStackTrace();
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("Content Length not Parsable");
            }
            setDeviceError("Content Length Not Reabable Error.");
            notifyDeviceErrorListeners();
            return null;

        }

        // newline;CR i think
        //
        if (contentLength > 1) {

            contentLength -= 2;
        }

        //dont do anythign more and return null if content length is nada or 1
        try {
            in = new BufferedInputStream(camServer.getInputStream());
        } catch (IOException iox) {
            iox.printStackTrace();
            setDeviceError("CamServer I/O Exception");
            notifyDeviceErrorListeners();
            return null;
        }


        int contentLengthCounter = contentLength;

        while (contentLengthCounter > 0) {
            int readLength = 0;

            byte[] byteBuffer = new byte[this.SERVER_CHUNK_READ_SIZE]; //1460

            try {
                readLength = in.read(byteBuffer);
            } catch (IOException iox) {
                iox.printStackTrace();

                setDeviceError("CamServer Data Read Error");
                notifyDeviceErrorListeners();
                return null;
            }

            if (readLength == -1) {

                contentLengthCounter = 0;
            } else {

            }

            imageByteArray.write(byteBuffer, 0, readLength);

            contentLengthCounter -= readLength;
        }

        return imageByteArray;
    }


    private void captureImage() {


        try {

            ByteArrayOutputStream imageByteArray = this.requestURLImage();

            if (this.getTransmitState() == this.STARTED) {

                outgoingBuffer.addLast(imageByteArray);
            }

            ((MonitorComponent) this.getMonitorComponent()).newImage(new ByteArrayInputStream(imageByteArray.toByteArray()));

            if (this.averageImageEncodeTime != 0) {

                this.averageImageEncodeTime =
                        (this.averageImageEncodeTime + this.imageEncodeTime) / 2;
            }

        } catch (Exception x) {
            x.printStackTrace();
            setDeviceError("Capture Image Error.");
            notifyDeviceErrorListeners();
            return;
        }


        if (bufferStarvedLock.isLocked()) {

            synchronized (bufferStarvedLock) {

                bufferStarvedLock.setLocked(false);

                bufferStarvedLock.notifyAll();
            }
        }
    }


    public long getAverageImageEncodeTime() {

        return this.averageImageEncodeTime;

    }


    public void obtainHardware() {

        this.setMonitorComponent(new MonitorComponent());

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("obtainHardware");
        }
    }

    public void startMonitorCapture() {

        this.setMonitorState(this.STARTED);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("startMonitorCapture");
        }

    }

    public void startMonitor() {

        if (this.getMonitorState() == this.STOPPED) {
            this.start();
        }

        this.setMonitorState(this.STARTED);


        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("startMonitor");
        }
    }

    public void stopMonitor() {

        this.setMonitorState(this.STOPPED);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("stopMonitor");
        }
    }

    public void startTransmit() {

        this.setTransmitState(this.STARTED);


        this.dispatchThread.start();

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("startTransmit");
        }

    }

    public boolean isTransmit() {

        return transmit;
    }

    public void setTransmit(boolean trans) {

        transmit = trans;
    }

    public int getOutgoingBufferSize() {

        return outgoingBuffer.size();
    }


    public int getMessagesSent() {

        return messagesSent;
    }

    public int getBytesSent() {

        return bytesSent;
    }


    public void releaseHardware() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("releaseHardware");
        }

        this.stopMonitor();

        this.stopMonitorCapture();


        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("Release Hardware");
        }
    }

    public void stopMonitorCapture() {

        this.setMonitorState(this.STOPPED);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("stopMonitorCapture");
        }
    }

    public void stopTransmit() {

        this.setTransmitState(this.STOPPED);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("stopTransmit");
        }
    }


    public int getBufferSize() {

        return this.outgoingBuffer.size();
    }

    public DialogMessage getNewTemplateMessage() {

        return (DialogMessage) this.templateMessage.clone();
    }

    public int getImagesPerMessage() {

        return this.imagesPerMessage;
    }

    public void setImagesPerMessage(int imagesPerMessage) {

        if (imagesPerMessage < this.MINIMUM_IMAGES_PER_MESSAGE) {

            this.imagesPerMessage = this.MINIMUM_IMAGES_PER_MESSAGE;
        } else {

            this.imagesPerMessage = imagesPerMessage;
        }
    }

    /**
     * Statistical Accessor. Returns the time delta of encoding an image
     */
    public long getImageEncodeTime() {

        return this.imageEncodeTime;
    }

    public void setImageCompression(int imageCompression) {

        if (imageCompression >= this.MINIMUM_IMAGE_COMPRESSION) {

            this.imageCompression = imageCompression;
        } else {
            this.imageCompression = this.MINIMUM_IMAGE_COMPRESSION;
        }
    }

    public int getImageCompression() {

        return imageCompression;
    }


    private int getMonitorState() {

        return this.monitorState;
    }

    private void setMonitorState(int monitorState) {

        this.monitorState = monitorState;
    }

    class MyLock extends Object {

        private boolean locked = false;

        public boolean isLocked() {
            return locked;
        }

        public void setLocked(boolean locked) {
            this.locked = locked;
        }
    }

    public Component getMonitorComponent() {

        return this.monitorComponent;
    }

    private void setMonitorComponent(MonitorComponent monitorComponent) {

        this.monitorComponent = monitorComponent;
    }


    class MonitorComponent extends JComponent {//Canvas {

        Dimension size = null;

        ImageReader reader = null;

        Image image = null;

        public MonitorComponent() {

            super();

            Iterator readers = ImageIO.getImageReadersByFormatName("jpeg");

            reader = (ImageReader) readers.next();


        }

        public Dimension getPreferredSize() {

            return size == null ? new Dimension(0, 0) : size;
        }

        private void setImageSize(Dimension size) {

            this.size = size;

            this.setSize(size);
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("imageSize " + size);
            }
        }


        public void paintComponent(Graphics g) {

            Graphics2D g2 = (Graphics2D) g;

            //super.paintComponent (g);

            g2.drawImage(image, 0, 0, size.width, size.height, null);

            //g.drawImage (image,0,0,null);

            g2.dispose();

        }

        public void newImage(ByteArrayInputStream encodedImageStream) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                //LOG.info("newImage");
            }

            try {

                long in = System.currentTimeMillis();

                // this is just a impl of ImageInputStreamImpl.. we can subclass
                // later and make our own
                MemoryCacheImageInputStream imageStream = new MemoryCacheImageInputStream(encodedImageStream);

                reader.setInput(imageStream);

                BufferedImage bufferedImage = reader.read(0);

                if (size == null) {

                    setImageSize(new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight()));
                }

                long out = System.currentTimeMillis();

                imageEncodeTime = out - in;

                in = System.currentTimeMillis();

                image = bufferedImage;

                //maybe imageUpdate???
                repaint();

                // Copy image to buffered image.
                /*
                Graphics g = this.getGraphics ();
                
                
                
                
                
                g.drawImage (bufferedImage, 0, 0, null);
                
                g.dispose ();
                */
                out = System.currentTimeMillis();


            } catch (Exception x) {
                x.printStackTrace();
                setDeviceError("Image Capture Error.");
                notifyDeviceErrorListeners();
                return;
            }
        }
    }

}
